package sma.iss;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sma.iss.sort.GeneticModelComparator;

public class YawModelRun {
	/** Logger for this class. */
	protected final static Logger LOGGER = LoggerFactory.getLogger(YawModelRun.class);

	protected Random rnd = new Random();
	static double MUTANT_FACTOR = 4.0;

	// the beta to work
	double beta = 75;

	@Test
	public void findBestYawForBeta() throws IOException {

		int nbGeneration = 11;
		LOGGER.info("findBestYawForBeta {}", beta);

		Collection<GeneticModel> population = new ArrayList<GeneticModel>();
		population.add(getNewModel(beta, 0.0));
		population.add(getNewModel(beta, 0.1));
		population.add(getNewModel(beta, 7.0));
		population.add(getNewModel(beta, 7.0 / 2.0));

		for (int gen = 1; gen <= nbGeneration; gen++) {

			if (gen != 1) {
				// Iterator<GeneticModel> iterator = population.iterator();
				// GeneticModel bestScorePos = iterator.next();
				// GeneticModel secondScorePos = iterator.next();
				// population.addAll(addChildren(bestScorePos, plusBeaux, 2));

				// children between 1st/3rd.
				List<GeneticModel> subList = new ArrayList<GeneticModel>(population);
				subList = subList.subList(0, Math.min(subList.size() - 1, Math.max(2, population.size() / 3)));
				population.addAll(addChildren(subList, 2));
			}
			// children between all
			population.addAll(addChildren(population, 2));
			if (gen != nbGeneration) {
				population.addAll(addMutant(population, 1));
			}

			// check transition is possible, remove bad
			removeImpossibleTransitions(population);
			calculateScores(population);

			// new population is sorted by score (or score * distance best)
			population = cleanPopulation(population);

		}

		LOGGER.info("The best yaw angle is {}", population.iterator().next().yaw);

	}


	private Collection<? extends GeneticModel> addMutant(Collection<GeneticModel> population, int nb) {
		Collection<GeneticModel> newChildren = new ArrayList<GeneticModel>(nb);
		Collection<GeneticModel> parents = getShuffled(population, nb);
		Iterator<GeneticModel> it = parents.iterator();
		for (int i = 0; i < nb; i++) {
			GeneticModel parent = it.next();
			newChildren.add(getNewModel(beta, parent.yaw + MUTANT_FACTOR * (rnd.nextDouble() - 0.5)));
		}
		return newChildren;
	}

	private Collection<GeneticModel> cleanPopulation(Collection<GeneticModel> population) {
		int nbToKeep = Math.max(4, population.size() / 2);

		Collection<GeneticModel> sorted = new TreeSet<GeneticModel>(new GeneticModelComparator());
		sorted.addAll(population);
		
		Collection<GeneticModel> result = new LinkedList<GeneticModel>();
		
		int nbAdded = 0;
		for (GeneticModel geneticModel : sorted) {
			result.add(geneticModel);
			nbAdded++;
			if (nbAdded >=nbToKeep) {
				break;
			}
		}
		LOGGER.info("------------------------------------ cleanPopulation result {} x \n{}", result.size(), result);
		return result;
	}

	private void calculateScores(Collection<GeneticModel> population) {
		for (GeneticModel geneticModel : population) {
			if (geneticModel.averageScore == Double.NEGATIVE_INFINITY) {
				// LOGGER.warn("temp imple");
				// geneticModel.averageScore = 10.0 - Math.pow(2.1 - geneticModel.yaw, 2.0);
				geneticModel.calculateListOfPosition(-22);
				geneticModel.calculateListOfPosition(0);
				int mnPerStep = 12;
				geneticModel.iterateOnMinutes(mnPerStep);
				geneticModel.enhanceMode = true;
				// recalculate position 0
				geneticModel.enhanceOneNewPosition(0);
				geneticModel.calculateAverageScore();
				LOGGER.info("                            calculateScores done for {}", geneticModel);
			}
		}

	}

	private void removeImpossibleTransitions(Collection<GeneticModel> population) {
		Iterator<GeneticModel> it = population.iterator();
		while (it.hasNext()) {
			GeneticModel geneticModel = it.next();
			if (geneticModel.yaw < 0.0 || geneticModel.yaw > 7.0) {
				it.remove();
			}

		}
	}

	private Collection<GeneticModel> addChildren(Collection<GeneticModel> population, int nb) {
		Collection<GeneticModel> newChildren = new ArrayList<GeneticModel>(nb);
		Collection<GeneticModel> parents = getShuffled(population, nb * 2);
		Iterator<GeneticModel> itParent = parents.iterator();
		for (int i = 0; i < nb; i++) {
			GeneticModel p1 = itParent.next();
			GeneticModel p2 = itParent.next();
			double coef = rnd.nextDouble();
			newChildren.add(getNewModel(beta, p1.yaw * coef + p2.yaw * (1.0 - coef)));
		}
		return newChildren;
	}

	private Collection<GeneticModel> getShuffled(Collection<GeneticModel> population, int minSize) {
		List<GeneticModel> melange = new ArrayList<GeneticModel>(minSize * 2);
		while (melange.size() < minSize) {
			List<GeneticModel> melange1 = new ArrayList<GeneticModel>(population);
			Collections.shuffle(melange1, rnd);
			melange.addAll(melange1);
		}
		return melange;

	}

	private GeneticModel getNewModel(double beta, double yaw) {
		GeneticModel dynamicModel = new GeneticModel();
		dynamicModel.nbKeepForNextTurn = 1;
		dynamicModel.nbAdvance = 1;
		dynamicModel.nbGeneration = 2;
		dynamicModel.setBeta(beta);
		dynamicModel.setYaw(yaw);
		return dynamicModel;
	}

	@Test
	public void findBestYawForAPosition() throws IOException {
		long startTime = System.currentTimeMillis();
		YawModel dynamicModel = new YawModel();
		dynamicModel.enhanceMode = true;
		dynamicModel.nbGeneration = 20;
		dynamicModel.nbAdvance = 2;
		dynamicModel.setBeta(-74);
		// dynamicModel.loadSimulation(simulationName);

		dynamicModel.optimizeYawAngleForMinute(40);

		long timeTaken = System.currentTimeMillis() - startTime;
		// LOGGER.info("end in {} ms, {} calculations (average {} ms)", timeTaken, dynamicModel.nbCaculationDone);
		LOGGER.info("runEnhanceInterleave end in {} ms, {} calculations (average {} ms)", timeTaken, dynamicModel.nbCaculationDone,
				timeTaken / dynamicModel.nbCaculationDone());
		// return dynamicModel.getSimulationName();
	}

}
